package board;

import character.MyCharacter;
import character.MyCharacterControl;
import character.ai.AIGeneralControl;
import com.jme3.effect.ParticleEmitter;
import com.jme3.math.ColorRGBA;
import com.jme3.math.FastMath;
import com.jme3.scene.Node;
import effect.AreaEffect;
import effect.EffectSpatial;
import game.CombatController;
import game.DataStore;
import game.GameState;
import game.GameUtil;
import game.PlayerRelationship;
import game.StateSwitcher;
import generator.Dungeon;
import generator.DungeonCell;
import generator.DungeonCellFloor;
import generator.DungeonCellWall;
import generator.DungeonGenerator;
import generator.DungeonRoom;
import generator.DungeonTunnel;
import item.Item;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
import java.util.PriorityQueue;
import java.util.Queue;
import java.util.Set;
import misc.Vector3i;
import player.Player;

public class Board extends Node {
    protected Map<Vector3i, Tile> board = new HashMap<Vector3i, Tile>();
    /** Contains highlighted tiles and a list of objects which are requesting them to be highlighted. */
    protected Map<Tile, Queue<HighlightRequest>> highlightedTileMap = new HashMap<Tile, Queue<HighlightRequest>>();
    protected Map<ParticleEmitter, Float> particleEffects = new HashMap<ParticleEmitter, Float>();

    public Board() {
        Dungeon dungeon = DungeonGenerator.generateDungeon(1);
        for(DungeonRoom room : dungeon.getRooms()) {
            ColorRGBA roomColor = ColorRGBA.randomColor();
            for(DungeonCell cell : room.getCells()) {
                 if(cell instanceof DungeonCellFloor)
                    addTileAtLocation(cell.getLocation()).highlight(roomColor);
                else if(cell instanceof DungeonCellWall)
                    addTileAtLocation(cell.getLocation()).highlight(ColorRGBA.Brown);
            }
        }
        for(DungeonTunnel tunnel : dungeon.getTunnels()) {
            ColorRGBA tunnelColor = ColorRGBA.randomColor();
            for(DungeonCell cell : tunnel.getCells()) {
                if(cell instanceof DungeonCellFloor)
                    addTileAtLocation(cell.getLocation()).highlight(tunnelColor);
                else if(cell instanceof DungeonCellWall)
                    addTileAtLocation(cell.getLocation()).highlight(ColorRGBA.Brown);
            }
        }
    }
    
    public Board(int x, int z) {
        Vector3i currentLoc;
	for(int i=0; i<x; i++) {
	    for(int j=0; j<z; j++) {
                currentLoc = new Vector3i(i,0,j);
		addTileAtLocation(currentLoc);
	    }
	}
    }
    
    public final Tile addTileAtLocation(Vector3i location) {
        Tile tile = new Tile(location);
        board.put(location, tile);
        attachChild(tile);
        return tile;
    }

    public Collection<Vector3i> getAllBoardLocations() {
	return board.keySet();
    }

    public boolean isTileAtLocation(Vector3i location) {
        return board.containsKey(location);
    }
    
    public Tile getTile(Vector3i location) {
        return board.get(location);
    }
    
    public Tile getRandomTile() {
        Object[] tiles = board.values().toArray();
        return (Tile)tiles[FastMath.rand.nextInt(tiles.length)];
    }
    
    public void addCharacter(MyCharacter character) {
	boolean forceVisible = false;
	for(Vector3i occupiedLocation : character.getOccupiedLocations()) {
	   boolean partOfCharVisible = board.get(occupiedLocation).addCharacter(character, forceVisible);
	   if(partOfCharVisible)
	       forceVisible = true;
	}
	attachChild(character);
	updateVisibleTiles(character.getPlayer());
	updateAIVision();
    }

    public void updateVisibleTiles(Player player) {
	player.updateVisibleLocations();
        
	if(player.equals(DataStore.player)) {
	    Set<Vector3i> visibleLocations = player.getVisibleLocations();
	    for(Tile tile : board.values()) {
		if(visibleLocations.contains(tile.getBoardLocation())) {
		    if(!tile.isDiscovered()) {
			tile.setDiscovered(true);
			tile.setVisible(true);
		    } else if(!tile.isVisible()) {
			tile.setVisible(true);
		    }
		} else if(!tile.hasSightTileModifiers()) {
		    if(tile.isVisible()) {
			tile.setVisible(false);
		    }
		}
	    }
	}
    }

    public void updateAIVision() {
	boolean startCombat = false;
        for(MyCharacter aiCharacter : GameUtil.getAllAICharactersInWorld()) {
            AIGeneralControl aiGeneralControl = aiCharacter.getControl(AIGeneralControl.class);
            Set<MyCharacter> oldVisibleCharacters = new HashSet<MyCharacter>(aiGeneralControl.getVisibleCharacters());
            Set<MyCharacter> newVisibleCharacters = new HashSet<MyCharacter>();
            for(Vector3i visibleLocation : aiCharacter.getVisibleLocations()) {
                for(MyCharacter visibleCharacter : board.get(visibleLocation).getCharacters()) {
                    if(!visibleCharacter.equals(aiCharacter)) {
                        if(!oldVisibleCharacters.contains(visibleCharacter))
                            newVisibleCharacters.add(visibleCharacter);
                        else
                            oldVisibleCharacters.remove(visibleCharacter);
                    }
                }
            }
            aiGeneralControl.removeVisibleCharacters(oldVisibleCharacters);
            aiGeneralControl.addVisibleCharacters(newVisibleCharacters);
            for(MyCharacter visibleCharacter : newVisibleCharacters) {
                //Check for hostile relationship and mutual awareness
                if(aiCharacter.getPlayer().getRelationship(visibleCharacter.getPlayer()) == PlayerRelationship.HOSTILE && visibleCharacter.getVisibleLocations().contains(aiCharacter.getBoardLocation())) {
                    startCombat = true;
                }
            }
        }

	if(StateSwitcher.getGameState() != GameState.COMBAT && startCombat)
	    CombatController.startCombat();
    }

    public Collection<MyCharacter> getCharactersAtLocation(Vector3i location) {
	return board.get(location).getCharacters();
    }

    public void addItem(Item item) {
        board.get(item.getBoardLocation()).setItem(item, false);
	attachChild(item);
    }
    
    protected void highlightBoardLocation(Vector3i boardLocation, HighlightRequest highlightRequest) {
        Tile tile = board.get(boardLocation);
        if(!highlightedTileMap.containsKey(tile)) {
            highlightedTileMap.put(tile, new PriorityQueue<HighlightRequest>(2, new HighlightRequestComparator()));
        }
        highlightedTileMap.get(tile).add(highlightRequest);
        updateTileHighlight(tile);
    }
    
    protected void highlightBoardLocations(Collection<Vector3i> boardLocations, HighlightRequest highlightRequest) {
	for(Vector3i boardlocation : boardLocations) {
            highlightBoardLocation(boardlocation, highlightRequest);
        }
    }

    protected void unhighlightBoardLocation(Vector3i boardLocation, HighlightRequest highlightRequest) {
	Tile tile = board.get(boardLocation);
	if(highlightedTileMap.containsKey(tile)) {
	    highlightedTileMap.get(tile).remove(highlightRequest);
	    if(highlightedTileMap.get(tile).isEmpty()) {
		highlightedTileMap.remove(tile);
	    }
	    updateTileHighlight(tile);
	}
    }

    protected void unhighlightBoardLocations(Collection<Vector3i> boardLocations, HighlightRequest highlightRequest) {
	for(Vector3i boardLocation : boardLocations) {
	    unhighlightBoardLocation(boardLocation, highlightRequest);
        }
    }

    protected void updateTileHighlight(Tile tile) {
	if(highlightedTileMap.get(tile) != null) {
	    tile.highlight(highlightedTileMap.get(tile).peek().getHighlightType());
	} else {
	    tile.unhighlight();
	}
    }
    
    public void characterIntendsToMove(MyCharacter character, Vector3i destination) {
	CombatController.characterMoving(character);
    }
    
    public void characterMoved(MyCharacter character, Collection<Vector3i> oldOccupiedLocations) {
	for(Vector3i oldOccupiedLocation : oldOccupiedLocations) {
	    Tile oldTile = board.get(oldOccupiedLocation);
	    oldTile.removeCharacter(character);
	}
	boolean forceVisible = false;
	for(Vector3i occupiedLocation : character.getOccupiedLocations()) {
	    Tile newTile = board.get(occupiedLocation);
	    boolean partOfCharVisible = newTile.addCharacter(character, forceVisible);
	    if(partOfCharVisible) {
	       forceVisible = true;
	    }
	    newTile.triggerTileWalkEffects(character);
	    if(newTile.getItem() != null) { //pick up item if one exists here
		pickUpItem(character, newTile.getItem());
		detachChild(newTile.getItem());
		newTile.removeItem();
	    }
	}
	unhighlightBoardLocation(character.getBoardLocation(), new HighlightRequest(character, HighlightType.MOVE));
	updateVisibleTiles(character.getPlayer());
	updateAIVision();
    }
    
    protected void pickUpItem(MyCharacter character, Item item) {
        character.getInventory().addItem(item);
        System.out.println(character+" picked up "+item);
    }

    public void highlightCharacterMoveLocations(MyCharacter character) {
	highlightBoardLocations(character.getControl(MyCharacterControl.class).getValidMoveLocations(), new HighlightRequest(character, HighlightType.RANGE));
    }

    public void unhighlightCharacterMoveLocations(MyCharacter character) {
	unhighlightBoardLocations(character.getControl(MyCharacterControl.class).getValidMoveLocations(), new HighlightRequest(character, HighlightType.RANGE));
    }
    
    public void highlightCharacterTravelPath(MyCharacter character, Collection<Vector3i> travelPath) {
        highlightBoardLocations(travelPath, new HighlightRequest(character, HighlightType.MOVE));
    }

    public void addEffectSpatial(MyCharacter character, EffectSpatial effectSpatial) {
	attachChild(effectSpatial);
        highlightBoardLocations(effectSpatial.getEffect().getLocationsInRange(), new HighlightRequest(character, HighlightType.RANGE));
    }

    public void moveEffectSpatial(EffectSpatial effectSpatial, Vector3i boardLocation) {
        if(effectSpatial.getEffect() instanceof AreaEffect) {
            AreaEffect areaEffect = (AreaEffect)effectSpatial.getEffect();
            if(!boardLocation.equals(areaEffect.getTarget())) {
                unhighlightBoardLocations(areaEffect.getAffectedLocations(), new HighlightRequest(effectSpatial, HighlightType.MOVE));
                areaEffect.setTarget(boardLocation);
                if(effectSpatial.getEffect().getLocationsInRange().contains(boardLocation))
                    highlightBoardLocations(areaEffect.getAffectedLocations(), new HighlightRequest(effectSpatial, HighlightType.MOVE));
            }
        }
    }

    public void removeEffectSpatial(MyCharacter character, EffectSpatial effectSpatial) {
        unhighlightBoardLocations(effectSpatial.getEffect().getLocationsInRange(), new HighlightRequest(character, HighlightType.RANGE));
        if(effectSpatial.getEffect() instanceof AreaEffect)
            unhighlightBoardLocations(((AreaEffect)effectSpatial.getEffect()).getAffectedLocations(), new HighlightRequest(effectSpatial, HighlightType.MOVE));
	detachChild(effectSpatial);
    }

    public void addParticleEffect(ParticleEmitter particleEffect, float particleEffectTime) {
	attachChild(particleEffect);
	particleEffects.put(particleEffect, particleEffectTime);
    }

    public void reduceParticleEffects(float tpf) {
	if(!particleEffects.isEmpty()) {
	    Iterator<Entry<ParticleEmitter, Float>> iter = particleEffects.entrySet().iterator();
	    while(iter.hasNext()) {
		Entry<ParticleEmitter, Float> entry = iter.next();
		entry.setValue(entry.getValue() - tpf);
		if(entry.getValue() <= 0f) {
		    detachChild(entry.getKey());
		    iter.remove();
		}
	    }
	}
    }
}
